/**
 *    Copyright 2009-2024 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.session;

import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.ResultMapResolver;
import org.apache.ibatis.builder.annotation.MethodResolver;
import org.apache.ibatis.builder.xml.XMLStatementBuilder;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.decorators.FifoCache;
import org.apache.ibatis.cache.decorators.LruCache;
import org.apache.ibatis.cache.decorators.SoftCache;
import org.apache.ibatis.cache.decorators.WeakCache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;
import org.apache.ibatis.datasource.pooled.PooledDataSourceFactory;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import org.apache.ibatis.executor.*;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.loader.ProxyFactory;
import org.apache.ibatis.executor.loader.cglib.CglibProxyFactory;
import org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl;
import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl;
import org.apache.ibatis.logging.log4j.Log4jImpl;
import org.apache.ibatis.logging.log4j2.Log4j2Impl;
import org.apache.ibatis.logging.nologging.NoLoggingImpl;
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.InterceptorChain;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.LanguageDriverRegistry;
import org.apache.ibatis.scripting.defaults.RawLanguageDriver;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeAliasRegistry;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.util.*;
import java.util.function.BiFunction;

/**
 * @author Clinton Begin
 */
public class Configuration {
  /**
   * <a href='https://mybatis.org/mybatis-3/zh/configuration.html#%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%EF%BC%88environments%EF%BC%89'>环境配置（environments）</a>
   * <p>
   * MyBatis 可以配置成适应多种环境，这种机制有助于将 SQL 映射应用于多种数据库之中， 现实情况下有多种理由需要这么做。例如，开发、测试和生产环境需要有不同的配置；或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。<br>
   * <li><b>尽管可以配置多个环境，但每个 SqlSessionFactory 实例只能选择一种环境。</b></li>
   * <br>
   * 为了指定创建哪种环境，只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是： <br>
   * <pre>
   *   SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
   *   SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
   * </pre>
   * 如果忽略了环境参数，那么将会加载默认环境，如下所示：
   * <pre>
   *   SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
   *   SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
   * </pre>
   * </p>
   * <p>
   *   environments 元素定义了如何配置环境。
   *   <pre>
   *     {@literal
   *        <environments default="development">
   *          <environment id="development">
   *            <transactionManager type="JDBC">
   *              <property name="..." value="..."/>
   *            </transactionManager>
   *             <dataSource type="POOLED">
   *              <property name="driver" value="${driver}"/>
   *              <property name="url" value="${url}"/>
   *              <property name="username" value="${username}"/>
   *              <property name="password" value="${password}"/>
   *            </dataSource>
   *          </environment>
   *        </environments>
   *     }
   *   </pre>
   *    注意一些关键点:
   *    <ul>
   *      <li> 默认使用的环境 ID（比如：default="development"）。 </li>
   *      <li> 每个 environment 元素定义的环境 ID（比如：id="development"）。 </li>
   *      <li> 事务管理器的配置（比如：type="JDBC"）。 </li>
   *      <li> 数据源的配置（比如：type="POOLED"）。 </li>
   *    </ul>
   *     默认环境和环境 ID 顾名思义。 环境可以随意命名，但务必保证默认的环境 ID 要匹配其中一个环境 ID。
   * </p>
   */
  protected Environment environment;

  /**
   * 这些属性可以在外部进行配置，并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性，也可以在 properties 元素的子元素中设置。例如：
   * <pre>
   *   {@literal
   *      <properties resource="org/mybatis/example/config.properties">
   *        <property name="username" value="dev_user"/>
   *        <property name="password" value="F2Fa3!33TYyg"/>
   *      </properties>
   *   }
   * </pre>
   * 设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:
   * <pre>
   *  {@literal
   *      <dataSource type="POOLED">
   *        <property name="driver" value="${driver}"/>
   *        <property name="url" value="${url}"/>
   *        <property name="username" value="${username}"/>
   *        <property name="password" value="${password}"/>
   *      </dataSource>
   *    }
   * </pre>
   * 这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换。 driver 和 url 属性将会由 config.properties 文件中对应的值来替换。这样就为配置提供了诸多灵活选择。
   * 也可以在 SqlSessionFactoryBuilder.build() 方法中传入属性值。例如：
   * <pre>
   *   {@literal
   *      SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
   *
   *      // ... 或者 ...
   *
   *      SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);
   *   }
   * </pre>
   * 1.Java类中定义；2.配置文件中<preperty>标签中定义；3.构建SqlSessionFactory时，方法入参中传递。
   * <p>
   * 如果一个属性在不只一个地方进行了配置，那么，MyBatis 将按照下面的顺序来加载：
   * <ui>
   * <li>首先读取在 properties 元素体内指定的属性。 </li>
   * <li>然后根据 properties 元素中的 resource 属性读取类路径下属性文件，或根据 url 属性指定的路径读取属性文件，并覆盖之前读取过的同名属性。 </li>
   * <li>最后读取作为方法参数传递的属性，并覆盖之前读取过的同名属性。 </li>
   * </ui>
   * </p>
   */
  protected Properties variables = new Properties();
  // 设置（settings）标签中的配置:这是 MyBatis 中极为重要的调整设置，它们会改变 MyBatis 的运行时行为。<a href='https://mybatis.org/mybatis-3/zh/configuration.html#%E8%AE%BE%E7%BD%AE%EF%BC%88settings%EF%BC%89'/>
  /**
   * 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。可选：true | false 。默认值：true
   */
  protected boolean cacheEnabled = true;
  /**
   * 延迟加载的全局开关。当开启时，所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。可选：true | false 。默认值：false
   */
  protected boolean lazyLoadingEnabled = false;
  /**
   * 开启时，任一方法的调用都会加载该对象的所有延迟加载属性。 否则，每个延迟加载属性会按需加载（参考 {@link #lazyLoadTriggerMethods})。可选：true | false 。默认值：false （在 3.4.1 及之前的版本中默认为 true）
   */
  protected boolean aggressiveLazyLoading;
  /**
   * 是否允许单个语句返回多结果集（需要数据库驱动支持）。 可选：true | false 。默认值：true
   */
  protected boolean multipleResultSetsEnabled = true;
  /**
   * 使用列标签代替列名。实际表现依赖于数据库驱动，具体可参考数据库驱动的相关文档，或通过对比测试来观察。 可选：true | false 。默认值：true
   */
  protected boolean useColumnLabel = true;
  /**
   * 允许 JDBC 支持自动生成主键，需要数据库驱动支持。如果设置为 true，将强制使用自动生成主键。尽管一些数据库驱动不支持此特性，但仍可正常工作（如 Derby）。 可选：true | false 。默认值：false
   */
  protected boolean useGeneratedKeys;
  /**
   * 指定 MyBatis 应如何自动映射列到字段或属性。
   * <ul>
   *   <li>NONE 表示关闭自动映射</li>
   *   <li>PARTIAL 只会自动映射没有定义嵌套结果映射的字段</li>
   *   <li>FULL 会自动映射任何复杂的结果集（无论是否嵌套）</li>
   * </ul>
   * 可选： NONE, PARTIAL, FULL  。默认值：PARTIAL
   */
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  /**
   * 指定发现自动映射目标未知列（或未知属性类型）的行为。
   * <ul>
   *   <li>NONE: 不做任何反应</li>
   *   <li>WARNING: 输出警告日志（'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN）</li>
   *   <li>FAILING: 映射失败 (抛出 SqlSessionException)</li>
   * </ul>
   * 可选：NONE, WARNING, FAILING  。默认值：NONE
   */
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  /**
   * 配置默认的执行器。
   * <ul>
   *   <li>SIMPLE 就是普通的执行器</li>
   *   <li>REUSE 执行器会重用预处理语句（PreparedStatement）</li>
   *   <li> BATCH 执行器不仅重用语句还会执行批量更新</li>
   * </ul>
   * 可选： SIMPLE, REUSE, BATCH  。默认值：SIMPLE
   */
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  /**
   * 设置超时时间，它决定数据库驱动等待数据库响应的秒数。 可选： 任意正整数 。默认值： 未设置 (null)
   */
  protected Integer defaultStatementTimeout;
  /**
   * 为驱动的结果集获取数量（fetchSize）设置一个建议值。此参数只可以在查询设置中被覆盖。  可选： 任意正整数 。默认值： 未设置 (null)
   */
  protected Integer defaultFetchSize;
  /**
   * 指定语句默认的滚动策略。（新增于 3.5.2）<br>
   * 可选： 	FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT（等同于未设置） 。默认值： 未设置 (null)
   */
  protected ResultSetType defaultResultSetType;
  /**
   * 是否允许在嵌套语句中使用分页（RowBounds）。如果允许使用则设置为 false。可选：true | false 。默认值：false
   */
  protected boolean safeRowBoundsEnabled;
  /**
   * 是否允许在嵌套语句中使用结果处理器（ResultHandler）。如果允许使用则设置为 false。 可选：true | false 。默认值：true
   */
  protected boolean safeResultHandlerEnabled = true;
  /**
   * 是否开启驼峰命名自动映射，即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。可选：true | false 。默认值：false
   */
  protected boolean mapUnderscoreToCamelCase;
  /**
   * MyBatis 利用本地缓存机制（Local Cache）防止循环引用和加速重复的嵌套查询。
   * <ul>
   *   <li>SESSION，会缓存一个会话中执行的所有查询</li>
   *   <li>STATEMENT，本地缓存将仅用于执行语句，对相同 SqlSession 的不同查询将不会进行缓存</li>
   * </ul>
   * 可选：SESSION | STATEMENT 。默认值：SESSION
   */
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  /**
   * 当没有为参数指定特定的 JDBC 类型时，空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型，多数情况直接用一般类型即可，比如 NULL、VARCHAR 或 OTHER。默认值：OTHER
   */
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  /**
   * 指定对象的哪些方法触发一次延迟加载。 默认值： equals,clone,hashCode,toString
   */
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  /**
   * 指定动态 SQL 生成使用的脚本语言注册器。<br>
   * defaultDriverClass： 指定动态 SQL 生成使用的默认脚本语言。 默认值： org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
   */
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  /**
   * typeHandlers 类型处理器
   * 无论是 MyBatis在预处理语句（PreparedStatement）中设置一个参数，还是从结果集中取出一个值时，都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
   * 类型处理器（typeHanflers）则用于java类型和jdbc类型映射，其中，mybatis自带的类型处理器基本上能够满足开发者在项目中的常用需求，不需要自定义类型处理器
   * <p>
   * 同时，你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。
   * 具体做法为：实现 org.apache.ibatis.type.TypeHandler 接口， 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler，
   * 然后可以选择性地将它映射到一个 JDBC 类型。
   *
   * <pre>
   *    //java实现类ExampleTypeHandler.java
   *    {@code @MappedJdbcTypes(JdbcType.VARCHAR)}
   *    public class ExampleTypeHandler extends BaseTypeHandler<String> {
   *
   *      {@code @Override}
   *      public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
   *        ps.setString(i, parameter);
   *      }
   *
   *      {@code @Override}
   *      public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
   *        return rs.getString(columnName);
   *      }
   *
   *      {@code @Override}
   *      public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
   *        return rs.getString(columnIndex);
   *      }
   *
   *      {@code @Override}
   *      public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
   *        return cs.getString(columnIndex);
   *      }
   *    }
   * </pre>
   * defaultEnumTypeHandler： 指定 Enum 使用的默认 TypeHandler 。（新增于 3.4.5） 。默认值： org.apache.ibatis.type.EnumTypeHandler
   */
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
  // 指定如果setter方法或Map的put方法时，将调用检索到的值是null。它是有用的，当你依靠Map.keySet（）或null初始化。注意原语（如整型，布尔等）不会被设置为null。
  /**
   * 指定当结果集中值为 null 的时候是否调用映射对象的 setter（map 对象时为 put）方法，这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。
   * 注意基本类型（int、boolean 等）是不能设置成 null 的。<br>
   * 可选： true | false 。默认值：false
   */
  protected boolean callSettersOnNulls;
  /**
   * 当返回行的所有列都是空时，MyBatis默认返回 {@code null}。 当开启这个设置时，MyBatis会返回一个空实例。 请注意，它也适用于嵌套的结果集（如集合或关联）。（新增于 3.4.2）<br>
   * 可选： true | false 。默认值：false
   */
  protected boolean returnInstanceForEmptyRow;
  /**
   * 指定 MyBatis 增加到日志名称的前缀。可选： 任何字符串 。默认值：未设置
   */
  protected String logPrefix;
  /**
   * 指定 MyBatis 所用日志的具体实现，未指定时将自动查找。<br>
   * 可选：SLF4J | LOG4J（3.5.9 起废弃） | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 。默认值：未设置
   */
  protected Class<? extends Log> logImpl;
  /**
   * 指定 Mybatis 创建可延迟加载对象所用到的代理工具。<br>
   * 可选： CGLIB （3.5.10 起废弃） | JAVASSIST 。默认值： JAVASSIST （MyBatis 3.3 以上）
   */
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
  /**
   * 指定 VFS 的实现 。<br>
   * 可选： 	自定义 VFS 的实现的类全限定名，以逗号分隔。 默认值：未设置
   */
  protected Class<? extends VFS> vfsImpl;
  /**
   * 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性，你的项目必须采用 Java 8 编译，并且加上 -parameters 选项。（新增于 3.4.1） <br>
   * 可选值： true | false 。默认值：true
   */
  protected boolean useActualParamName = true;
  /**
   * 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。
   * 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。（新增于 3.2.3）。<br>
   * 可选： 一个类型别名或完全限定类名。 。默认值：未设置
   * <p>
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
   */
  protected Class<?> configurationFactory;
  /**
   * 从SQL中删除多余的空格字符。请注意，这也会影响SQL中的文字字符串。 (新增于 3.5.5) <br>
   * 可选值： true | false 。默认值：false
   */
  protected boolean shrinkWhitespacesInSql;
  /**
   * 指定一个拥有 provider 方法的 sql provider 类 （新增于 3.5.6）. 这个类适用于指定 sql provider 注解上的type（或 value） 属性（当这些属性在注解中被忽略时）。 (e.g. @SelectProvider)<br>
   * 可选： 一个类型别名或完全限定类名。 。默认值：未设置
   */
  protected Class<?> defaultSqlProviderType;

  /**
   * <a href='https://mybatis.org/mybatis-3/zh/configuration.html#%E7%B1%BB%E5%9E%8B%E5%88%AB%E5%90%8D%EF%BC%88typealiases%EF%BC%89'>类型别名（typeAliases）</a>
   * 可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置，意在降低冗余的全限定类名书写。<br>
   * 例如：
   * <pre>
   *   {@literal
   *      <typeAliases>
   *        <typeAlias alias="Author" type="domain.blog.Author"/>
   *        <typeAlias alias="Blog" type="domain.blog.Blog"/>
   *      </typeAliases>
   *   }
   * </pre>
   * 当这样配置时，Blog 可以用在任何使用 domain.blog.Blog 的地方。<br>
   * 也可以指定一个包名，MyBatis 会在包名下面搜索需要的 Java Bean，比如：
   * <pre>
   *   {@literal
   *    <typeAliases>
   *        <package name="domain.blog"/>
   *    </typeAliases>
   *   }
   *  </pre>
   * 每一个在包 domain.blog 中的 Java Bean，在没有注解的情况下，会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author；若有注解，则别名为其注解值。见下面的例子：
   * <pre>
   *    {@literal
   *    @Alias("author")
   *    public class Author {
   *        ...
   *    }
   *    }
   *  </pre>
   */
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

  /**
   * <a href='https://mybatis.org/mybatis-3/zh/configuration.html#%E5%AF%B9%E8%B1%A1%E5%B7%A5%E5%8E%82%EF%BC%88objectfactory%EF%BC%89'>对象工厂（objectFactory）</a>
   * <p>
   * 每次 MyBatis 创建结果对象的新实例时，它都会使用一个对象工厂（ObjectFactory）实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类，要么通过默认无参构造方法，要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为，可以通过创建自己的对象工厂来实现。<br>
   * 比如：
   * <pre>
   *     // ExampleObjectFactory.java
   * public class ExampleObjectFactory extends DefaultObjectFactory {
   *
   *   public {@literal <T>} T create(Class{@literal <T>} type) {
   *     return super.create(type);
   *   }
   *
   *   {@literal @Override}
   *   public {@literal <T>} T create(Class{@literal <T>} type, List<{@literal Class<T>}> constructorArgTypes, List<{@literal Object}> constructorArgs) {
   *     return super.create(type, constructorArgTypes, constructorArgs);
   *   }
   *
   *   {@literal @Override}
   *   public void setProperties(Properties properties) {
   *     super.setProperties(properties);
   *   }
   *
   *   {@literal @Override}
   *   public {@literal <T>} boolean isCollection(Class{@literal <T>} type) {
   *     return Collection.class.isAssignableFrom(type);
   *   }}
   *   </pre>
   * <pre>
   *    {@literal
   *      <!-- mybatis-config.xml -->
   *      <objectFactory type="org.mybatis.example.ExampleObjectFactory">
   *        <property name="someProperty" value="100"/>
   *      </objectFactory>
   *    }
   *  </pre>
   * </p>
   * ObjectFactory 接口很简单，它包含两个创建实例用的方法，一个是处理默认无参构造方法的，另外一个是处理带参数的构造方法的。 另外，setProperties 方法可以被用来配置 ObjectFactory，在初始化你的 ObjectFactory 实例后， objectFactory 元素体中定义的属性会被传递给 setProperties 方法。
   */
  protected ObjectFactory objectFactory = new DefaultObjectFactory();

  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  protected String databaseId;

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  /**
   * <a href='https://mybatis.org/mybatis-3/zh/configuration.html#%E6%8F%92%E4%BB%B6%EF%BC%88plugins%EF%BC%89'>插件（plugins）</a>
   * MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下，MyBatis 允许使用插件来拦截的方法调用包括：
   * <ol>
   *   <li> Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) </li>
   *   <li> ParameterHandler (getParameterObject, setParameters) /li>
   *   <li> ResultSetHandler (handleResultSets, handleOutputParameters) </li>
   *   <li> StatementHandler (prepare, parameterize, batch, update, query) </li>
   * </ol>
   * 这些类中方法的细节可以通过查看每个方法的签名来发现，或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用，那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时，很可能会破坏 MyBatis 的核心模块。
   * 这些都是更底层的类和方法，所以使用插件的时候要特别当心。
   * <p>
   *   通过 MyBatis 提供的强大机制，使用插件是非常简单的，只需实现 Interceptor 接口，并指定想要拦截的方法签名即可。
   *   <pre>
   *     {@literal
   *       // ExamplePlugin.java
   *       @Intercepts({@Signature(
   *         type= Executor.class,
   *         method = "update",
   *         args = {MappedStatement.class,Object.class})})
   *       public class ExamplePlugin implements Interceptor {
   *         private Properties properties = new Properties();
   *
   *        {@literal @Override}
   *         public Object intercept(Invocation invocation) throws Throwable {
   *           // implement pre processing if need
   *           Object returnObject = invocation.proceed();
   *           // implement post processing if need
   *           return returnObject;
   *         }
   *
   *        {@literal @Override}
   *         public void setProperties(Properties properties) {
   *           this.properties = properties;
   *         }
   *       }
   *     }
   *   </pre>
   *
   *   <pre>
   *     {@literal
   *       <!-- mybatis-config.xml -->
   *       <plugins>
   *         <plugin interceptor="org.mybatis.example.ExamplePlugin">
   *           <property name="someProperty" value="100"/>
   *         </plugin>
   *       </plugins>
   *     }
   *   </pre>
   *   上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用， 这里的 Executor 是负责执行底层映射语句的内部对象。
   * </p>
   */
  protected final InterceptorChain interceptorChain = new InterceptorChain();

  /**
   * Mapper.xml映射文件中定义了操作数据库的sql，每个sql是一个statement，映射文件是mybatis的核心。<br>
   * 一共有四种方法配置映射文件的资源路径：
   *   <pre>
   *     {@literal
   *     <!-- 使用相对于类路径的资源引用 -->
   *      <mappers>
   *          <mapper resource="sqlmap/User.xml"/>
   *      </mappers>
   *
   *      <!-- 使用完全限定资源定位符（URL） -->
   *      <mappers>
   *          <mapper url="file:///var/project/config/sqlmap/User.xml"/>
   *          <mapper url="file:///var/mappers/BlogMapper.xml"/>
   *          <mapper url="file:///var/mappers/PostMapper.xml"/>
   *      </mappers>
   *      <!--windows系统下 -->
   *          <mapper url="file:///D:\workspace\project\config\sqlmap\User.xml" />
   *
   *      <!-- 使用映射器接口实现类的完全限定类名 -->
   *      <mappers>
   *            <mapper class="org.mybatis.builder.BlogMapper"/>
   *            <mapper class="org.mybatis.builder.PostMapper"/>
   *      </mappers>
   *
   *      <!-- 将包内的映射器接口实现全部注册为映射器 -->
   *      <mappers>
   *          <package name="org.mybatis.builder"/>
   *      </mappers>
   *     }
   *   </pre>
   */
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection").conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  /** 不完整的结果映射 */
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

  /*
   * A map holds cache-ref relationship. The key is the namespace that
   * references a cache bound to another namespace and the value is the
   * namespace which the actual cache is bound to.
   */
  protected final Map<String, String> cacheRefMap = new HashMap<>();

  public Configuration(Environment environment) {
    this();
    this.environment = environment;
  }

  public Configuration() {
    // 事务工厂
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    // 数据源工厂
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    // 缓存
    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
    // 日志
    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    // 代理
    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }

  public String getLogPrefix() {
    return logPrefix;
  }

  public void setLogPrefix(String logPrefix) {
    this.logPrefix = logPrefix;
  }

  public Class<? extends Log> getLogImpl() {
    return logImpl;
  }

  public void setLogImpl(Class<? extends Log> logImpl) {
    if (logImpl != null) {
      this.logImpl = logImpl;
      LogFactory.useCustomLogging(this.logImpl);
    }
  }

  public Class<? extends VFS> getVfsImpl() {
    return this.vfsImpl;
  }

  public void setVfsImpl(Class<? extends VFS> vfsImpl) {
    if (vfsImpl != null) {
      this.vfsImpl = vfsImpl;
      VFS.addImplClass(this.vfsImpl);
    }
  }

  /**
   * Gets an applying type when omit a type on sql provider annotation(e.g. {@link org.apache.ibatis.annotations.SelectProvider}).
   *
   * @return the default type for sql provider annotation
   * @since 3.5.6
   */
  public Class<?> getDefaultSqlProviderType() {
    return defaultSqlProviderType;
  }

  /**
   * Sets an applying type when omit a type on sql provider annotation(e.g. {@link org.apache.ibatis.annotations.SelectProvider}).
   *
   * @param defaultSqlProviderType the default type for sql provider annotation
   * @since 3.5.6
   */
  public void setDefaultSqlProviderType(Class<?> defaultSqlProviderType) {
    this.defaultSqlProviderType = defaultSqlProviderType;
  }

  public boolean isCallSettersOnNulls() {
    return callSettersOnNulls;
  }

  public void setCallSettersOnNulls(boolean callSettersOnNulls) {
    this.callSettersOnNulls = callSettersOnNulls;
  }

  public boolean isUseActualParamName() {
    return useActualParamName;
  }

  public void setUseActualParamName(boolean useActualParamName) {
    this.useActualParamName = useActualParamName;
  }

  public boolean isReturnInstanceForEmptyRow() {
    return returnInstanceForEmptyRow;
  }

  public void setReturnInstanceForEmptyRow(boolean returnEmptyInstance) {
    this.returnInstanceForEmptyRow = returnEmptyInstance;
  }

  public boolean isShrinkWhitespacesInSql() {
    return shrinkWhitespacesInSql;
  }

  public void setShrinkWhitespacesInSql(boolean shrinkWhitespacesInSql) {
    this.shrinkWhitespacesInSql = shrinkWhitespacesInSql;
  }

  public String getDatabaseId() {
    return databaseId;
  }

  public void setDatabaseId(String databaseId) {
    this.databaseId = databaseId;
  }

  public Class<?> getConfigurationFactory() {
    return configurationFactory;
  }

  public void setConfigurationFactory(Class<?> configurationFactory) {
    this.configurationFactory = configurationFactory;
  }

  public boolean isSafeResultHandlerEnabled() {
    return safeResultHandlerEnabled;
  }

  public void setSafeResultHandlerEnabled(boolean safeResultHandlerEnabled) {
    this.safeResultHandlerEnabled = safeResultHandlerEnabled;
  }

  public boolean isSafeRowBoundsEnabled() {
    return safeRowBoundsEnabled;
  }

  public void setSafeRowBoundsEnabled(boolean safeRowBoundsEnabled) {
    this.safeRowBoundsEnabled = safeRowBoundsEnabled;
  }

  public boolean isMapUnderscoreToCamelCase() {
    return mapUnderscoreToCamelCase;
  }

  public void setMapUnderscoreToCamelCase(boolean mapUnderscoreToCamelCase) {
    this.mapUnderscoreToCamelCase = mapUnderscoreToCamelCase;
  }

  public void addLoadedResource(String resource) {
    loadedResources.add(resource);
  }

  public boolean isResourceLoaded(String resource) {
    return loadedResources.contains(resource);
  }

  public Environment getEnvironment() {
    return environment;
  }

  public void setEnvironment(Environment environment) {
    this.environment = environment;
  }

  public AutoMappingBehavior getAutoMappingBehavior() {
    return autoMappingBehavior;
  }

  public void setAutoMappingBehavior(AutoMappingBehavior autoMappingBehavior) {
    this.autoMappingBehavior = autoMappingBehavior;
  }

  /**
   * Gets the auto mapping unknown column behavior.
   *
   * @return the auto mapping unknown column behavior
   * @since 3.4.0
   */
  public AutoMappingUnknownColumnBehavior getAutoMappingUnknownColumnBehavior() {
    return autoMappingUnknownColumnBehavior;
  }

  /**
   * Sets the auto mapping unknown column behavior.
   *
   * @param autoMappingUnknownColumnBehavior the new auto mapping unknown column behavior
   * @since 3.4.0
   */
  public void setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior) {
    this.autoMappingUnknownColumnBehavior = autoMappingUnknownColumnBehavior;
  }

  public boolean isLazyLoadingEnabled() {
    return lazyLoadingEnabled;
  }

  public void setLazyLoadingEnabled(boolean lazyLoadingEnabled) {
    this.lazyLoadingEnabled = lazyLoadingEnabled;
  }

  public ProxyFactory getProxyFactory() {
    return proxyFactory;
  }

  public void setProxyFactory(ProxyFactory proxyFactory) {
    if (proxyFactory == null) {
      proxyFactory = new JavassistProxyFactory();
    }
    this.proxyFactory = proxyFactory;
  }

  public boolean isAggressiveLazyLoading() {
    return aggressiveLazyLoading;
  }

  public void setAggressiveLazyLoading(boolean aggressiveLazyLoading) {
    this.aggressiveLazyLoading = aggressiveLazyLoading;
  }

  public boolean isMultipleResultSetsEnabled() {
    return multipleResultSetsEnabled;
  }

  public void setMultipleResultSetsEnabled(boolean multipleResultSetsEnabled) {
    this.multipleResultSetsEnabled = multipleResultSetsEnabled;
  }

  public Set<String> getLazyLoadTriggerMethods() {
    return lazyLoadTriggerMethods;
  }

  public void setLazyLoadTriggerMethods(Set<String> lazyLoadTriggerMethods) {
    this.lazyLoadTriggerMethods = lazyLoadTriggerMethods;
  }

  public boolean isUseGeneratedKeys() {
    return useGeneratedKeys;
  }

  public void setUseGeneratedKeys(boolean useGeneratedKeys) {
    this.useGeneratedKeys = useGeneratedKeys;
  }

  public ExecutorType getDefaultExecutorType() {
    return defaultExecutorType;
  }

  public void setDefaultExecutorType(ExecutorType defaultExecutorType) {
    this.defaultExecutorType = defaultExecutorType;
  }

  public boolean isCacheEnabled() {
    return cacheEnabled;
  }

  public void setCacheEnabled(boolean cacheEnabled) {
    this.cacheEnabled = cacheEnabled;
  }

  public Integer getDefaultStatementTimeout() {
    return defaultStatementTimeout;
  }

  public void setDefaultStatementTimeout(Integer defaultStatementTimeout) {
    this.defaultStatementTimeout = defaultStatementTimeout;
  }

  /**
   * Gets the default fetch size.
   *
   * @return the default fetch size
   * @since 3.3.0
   */
  public Integer getDefaultFetchSize() {
    return defaultFetchSize;
  }

  /**
   * Sets the default fetch size.
   *
   * @param defaultFetchSize the new default fetch size
   * @since 3.3.0
   */
  public void setDefaultFetchSize(Integer defaultFetchSize) {
    this.defaultFetchSize = defaultFetchSize;
  }

  /**
   * Gets the default result set type.
   *
   * @return the default result set type
   * @since 3.5.2
   */
  public ResultSetType getDefaultResultSetType() {
    return defaultResultSetType;
  }

  /**
   * Sets the default result set type.
   *
   * @param defaultResultSetType the new default result set type
   * @since 3.5.2
   */
  public void setDefaultResultSetType(ResultSetType defaultResultSetType) {
    this.defaultResultSetType = defaultResultSetType;
  }

  public boolean isUseColumnLabel() {
    return useColumnLabel;
  }

  public void setUseColumnLabel(boolean useColumnLabel) {
    this.useColumnLabel = useColumnLabel;
  }

  public LocalCacheScope getLocalCacheScope() {
    return localCacheScope;
  }

  public void setLocalCacheScope(LocalCacheScope localCacheScope) {
    this.localCacheScope = localCacheScope;
  }

  public JdbcType getJdbcTypeForNull() {
    return jdbcTypeForNull;
  }

  public void setJdbcTypeForNull(JdbcType jdbcTypeForNull) {
    this.jdbcTypeForNull = jdbcTypeForNull;
  }

  public Properties getVariables() {
    return variables;
  }

  public void setVariables(Properties variables) {
    this.variables = variables;
  }

  public TypeHandlerRegistry getTypeHandlerRegistry() {
    return typeHandlerRegistry;
  }

  /**
   * Set a default {@link TypeHandler} class for {@link Enum}.
   * A default {@link TypeHandler} is {@link org.apache.ibatis.type.EnumTypeHandler}.
   *
   * @param typeHandler a type handler class for {@link Enum}
   * @since 3.4.5
   */
  public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) {
    if (typeHandler != null) {
      getTypeHandlerRegistry().setDefaultEnumTypeHandler(typeHandler);
    }
  }

  public TypeAliasRegistry getTypeAliasRegistry() {
    return typeAliasRegistry;
  }

  /**
   * Gets the mapper registry.
   *
   * @return the mapper registry
   * @since 3.2.2
   */
  public MapperRegistry getMapperRegistry() {
    return mapperRegistry;
  }

  public ReflectorFactory getReflectorFactory() {
    return reflectorFactory;
  }

  public void setReflectorFactory(ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
  }

  public ObjectFactory getObjectFactory() {
    return objectFactory;
  }

  public void setObjectFactory(ObjectFactory objectFactory) {
    this.objectFactory = objectFactory;
  }

  public ObjectWrapperFactory getObjectWrapperFactory() {
    return objectWrapperFactory;
  }

  public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
    this.objectWrapperFactory = objectWrapperFactory;
  }

  /**
   * Gets the interceptors.
   *
   * @return the interceptors
   * @since 3.2.2
   */
  public List<Interceptor> getInterceptors() {
    return interceptorChain.getInterceptors();
  }

  public LanguageDriverRegistry getLanguageRegistry() {
    return languageRegistry;
  }

  public void setDefaultScriptingLanguage(Class<? extends LanguageDriver> driver) {
    if (driver == null) {
      driver = XMLLanguageDriver.class;
    }
    getLanguageRegistry().setDefaultDriverClass(driver);
  }

  public LanguageDriver getDefaultScriptingLanguageInstance() {
    return languageRegistry.getDefaultDriver();
  }

  /**
   * Gets the language driver.
   *
   * @param langClass the lang class
   * @return the language driver
   * @since 3.5.1
   */
  public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
    if (langClass == null) {
      return languageRegistry.getDefaultDriver();
    }
    languageRegistry.register(langClass);
    return languageRegistry.getDriver(langClass);
  }

  /**
   * Gets the default scripting language instance.
   *
   * @return the default scripting language instance
   * @deprecated Use {@link #getDefaultScriptingLanguageInstance()}
   */
  @Deprecated
  public LanguageDriver getDefaultScriptingLanuageInstance() {
    return getDefaultScriptingLanguageInstance();
  }

  public MetaObject newMetaObject(Object object) {
    return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
  }

  /**
   * 创建参数处理器
   *
   * @param mappedStatement 被映射的sql语句
   * @param parameterObject 参数对象
   * @param boundSql
   * @return
   */
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 处理拦截器。@Signature(type = StatementHandler.class, method = "query", args = {...}
    // 处理 StatementHandler 接口的拦截器
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 根据executor类型创建对象的Executor对象
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    // 如果cacheEnabled属性为ture，则使用 CachingExecutor对上面创建的Executor进行装饰
    // 这里用到了装饰器模式。如果开启了缓存，用 CachingExecutor 将实际的执行器装饰起来做增强
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 处理拦截器。@Signature(type = Executor.class, method = "query", args = {...}
    // 调用pluginAll()方法为当前接口创建动态代理对象Plugin，实际会调用Plugin.invoke()方法增强后再执行原方法
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

  public void addKeyGenerator(String id, KeyGenerator keyGenerator) {
    keyGenerators.put(id, keyGenerator);
  }

  public Collection<String> getKeyGeneratorNames() {
    return keyGenerators.keySet();
  }

  public Collection<KeyGenerator> getKeyGenerators() {
    return keyGenerators.values();
  }

  public KeyGenerator getKeyGenerator(String id) {
    return keyGenerators.get(id);
  }

  public boolean hasKeyGenerator(String id) {
    return keyGenerators.containsKey(id);
  }

  public void addCache(Cache cache) {
    caches.put(cache.getId(), cache);
  }

  public Collection<String> getCacheNames() {
    return caches.keySet();
  }

  public Collection<Cache> getCaches() {
    return caches.values();
  }

  public Cache getCache(String id) {
    return caches.get(id);
  }

  public boolean hasCache(String id) {
    return caches.containsKey(id);
  }

  public void addResultMap(ResultMap rm) {
    resultMaps.put(rm.getId(), rm);
    checkLocallyForDiscriminatedNestedResultMaps(rm);
    checkGloballyForDiscriminatedNestedResultMaps(rm);
  }

  public Collection<String> getResultMapNames() {
    return resultMaps.keySet();
  }

  public Collection<ResultMap> getResultMaps() {
    return resultMaps.values();
  }

  public ResultMap getResultMap(String id) {
    return resultMaps.get(id);
  }

  public boolean hasResultMap(String id) {
    return resultMaps.containsKey(id);
  }

  public void addParameterMap(ParameterMap pm) {
    parameterMaps.put(pm.getId(), pm);
  }

  public Collection<String> getParameterMapNames() {
    return parameterMaps.keySet();
  }

  public Collection<ParameterMap> getParameterMaps() {
    return parameterMaps.values();
  }

  public ParameterMap getParameterMap(String id) {
    return parameterMaps.get(id);
  }

  public boolean hasParameterMap(String id) {
    return parameterMaps.containsKey(id);
  }

  public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

  public Collection<String> getMappedStatementNames() {
    buildAllStatements();
    return mappedStatements.keySet();
  }

  public Collection<MappedStatement> getMappedStatements() {
    buildAllStatements();
    return mappedStatements.values();
  }

  public Collection<XMLStatementBuilder> getIncompleteStatements() {
    return incompleteStatements;
  }

  public void addIncompleteStatement(XMLStatementBuilder incompleteStatement) {
    incompleteStatements.add(incompleteStatement);
  }

  public Collection<CacheRefResolver> getIncompleteCacheRefs() {
    return incompleteCacheRefs;
  }

  public void addIncompleteCacheRef(CacheRefResolver incompleteCacheRef) {
    incompleteCacheRefs.add(incompleteCacheRef);
  }

  public Collection<ResultMapResolver> getIncompleteResultMaps() {
    return incompleteResultMaps;
  }

  public void addIncompleteResultMap(ResultMapResolver resultMapResolver) {
    incompleteResultMaps.add(resultMapResolver);
  }

  public void addIncompleteMethod(MethodResolver builder) {
    incompleteMethods.add(builder);
  }

  public Collection<MethodResolver> getIncompleteMethods() {
    return incompleteMethods;
  }

  public MappedStatement getMappedStatement(String id) {
    return this.getMappedStatement(id, true);
  }

  public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
    if (validateIncompleteStatements) {
      buildAllStatements();
    }
    return mappedStatements.get(id);
  }

  public Map<String, XNode> getSqlFragments() {
    return sqlFragments;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

  public void addMappers(String packageName, Class<?> superType) {
    mapperRegistry.addMappers(packageName, superType);
  }

  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }

  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

  public boolean hasMapper(Class<?> type) {
    return mapperRegistry.hasMapper(type);
  }

  public boolean hasStatement(String statementName) {
    return hasStatement(statementName, true);
  }

  public boolean hasStatement(String statementName, boolean validateIncompleteStatements) {
    if (validateIncompleteStatements) {
      buildAllStatements();
    }
    return mappedStatements.containsKey(statementName);
  }

  public void addCacheRef(String namespace, String referencedNamespace) {
    cacheRefMap.put(namespace, referencedNamespace);
  }

  /*
   * Parses all the unprocessed statement nodes in the cache. It is recommended
   * to call this method once all the mappers are added as it provides fail-fast
   * statement validation.
   */
  protected void buildAllStatements() {
    parsePendingResultMaps();
    if (!incompleteCacheRefs.isEmpty()) {
      synchronized (incompleteCacheRefs) {
        incompleteCacheRefs.removeIf(x -> x.resolveCacheRef() != null);
      }
    }
    if (!incompleteStatements.isEmpty()) {
      synchronized (incompleteStatements) {
        incompleteStatements.removeIf(x -> {
          x.parseStatementNode();
          return true;
        });
      }
    }
    if (!incompleteMethods.isEmpty()) {
      synchronized (incompleteMethods) {
        incompleteMethods.removeIf(x -> {
          x.resolve();
          return true;
        });
      }
    }
  }

  private void parsePendingResultMaps() {
    if (incompleteResultMaps.isEmpty()) {
      return;
    }
    synchronized (incompleteResultMaps) {
      boolean resolved;
      IncompleteElementException ex = null;
      do {
        resolved = false;
        Iterator<ResultMapResolver> iterator = incompleteResultMaps.iterator();
        while (iterator.hasNext()) {
          try {
            iterator.next().resolve();
            iterator.remove();
            resolved = true;
          } catch (IncompleteElementException e) {
            ex = e;
          }
        }
      } while (resolved);
      if (!incompleteResultMaps.isEmpty() && ex != null) {
        // At least one result map is unresolvable.
        throw ex;
      }
    }
  }

  /**
   * Extracts namespace from fully qualified statement id.
   *
   * @param statementId the statement id
   * @return namespace or null when id does not contain period.
   */
  protected String extractNamespace(String statementId) {
    int lastPeriod = statementId.lastIndexOf('.');
    return lastPeriod > 0 ? statementId.substring(0, lastPeriod) : null;
  }

  // Slow but a one time cost. A better solution is welcome.
  protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
    if (rm.hasNestedResultMaps()) {
      for (Map.Entry<String, ResultMap> entry : resultMaps.entrySet()) {
        Object value = entry.getValue();
        if (value instanceof ResultMap) {
          ResultMap entryResultMap = (ResultMap) value;
          if (!entryResultMap.hasNestedResultMaps() && entryResultMap.getDiscriminator() != null) {
            Collection<String> discriminatedResultMapNames = entryResultMap.getDiscriminator().getDiscriminatorMap().values();
            if (discriminatedResultMapNames.contains(rm.getId())) {
              entryResultMap.forceNestedResultMaps();
            }
          }
        }
      }
    }
  }

  // Slow but a one time cost. A better solution is welcome.
  protected void checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm) {
    if (!rm.hasNestedResultMaps() && rm.getDiscriminator() != null) {
      for (Map.Entry<String, String> entry : rm.getDiscriminator().getDiscriminatorMap().entrySet()) {
        String discriminatedResultMapName = entry.getValue();
        if (hasResultMap(discriminatedResultMapName)) {
          ResultMap discriminatedResultMap = resultMaps.get(discriminatedResultMapName);
          if (discriminatedResultMap.hasNestedResultMaps()) {
            rm.forceNestedResultMaps();
            break;
          }
        }
      }
    }
  }

  protected static class StrictMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -4950446264854982944L;
    private final String name;
    private BiFunction<V, V, String> conflictMessageProducer;

    public StrictMap(String name, int initialCapacity, float loadFactor) {
      super(initialCapacity, loadFactor);
      this.name = name;
    }

    public StrictMap(String name, int initialCapacity) {
      super(initialCapacity);
      this.name = name;
    }

    public StrictMap(String name) {
      super();
      this.name = name;
    }

    public StrictMap(String name, Map<String, ? extends V> m) {
      super(m);
      this.name = name;
    }

    /**
     * Assign a function for producing a conflict error message when contains value with the same key.
     * <p>
     * function arguments are 1st is saved value and 2nd is target value.
     *
     * @param conflictMessageProducer A function for producing a conflict error message
     * @return a conflict error message
     * @since 3.5.0
     */
    public StrictMap<V> conflictMessageProducer(BiFunction<V, V, String> conflictMessageProducer) {
      this.conflictMessageProducer = conflictMessageProducer;
      return this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
      if (containsKey(key)) {
        throw new IllegalArgumentException(name + " already contains value for " + key + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
      }
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          super.put(shortKey, value);
        } else {
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      return super.put(key, value);
    }

    @Override
    public V get(Object key) {
      V value = super.get(key);
      if (value == null) {
        throw new IllegalArgumentException(name + " does not contain value for " + key);
      }
      if (value instanceof Ambiguity) {
        throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name + " (try using the full name including the namespace, or rename one of the entries)");
      }
      return value;
    }

    protected static class Ambiguity {
      private final String subject;

      public Ambiguity(String subject) {
        this.subject = subject;
      }

      public String getSubject() {
        return subject;
      }
    }

    private String getShortName(String key) {
      final String[] keyParts = key.split("\\.");
      return keyParts[keyParts.length - 1];
    }
  }

}
